package jcer.core;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.util.Iterator;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class EventDriver {
	static Log logger = LogFactory.getLog(EventDriver.class);

	Selector selector;
	ServerSocketChannel socket;
	int workNum;
	int maxConnection;// max concurrent connections
	int currentConnectionCount = 0;

	// connection create
	BaseEventHandler openHandler = new ConnectionOpenHandler(this);
	// connection close
	BaseEventHandler closeHandler = new ConnectionCloseHandler(this);
	// connection can read
	BaseEventHandler readHandler = new ConnectionReadHandler(this);
	// connection can write
	BaseEventHandler writeHandler = new ConnectionWriteHandler(this);
	// task execute
	BaseEventHandler taskHandler = new TaskHandler(this);
	// a request which is executed is done
	BaseEventHandler taskComplatedHandler = new TaskComplatedHandler(this);

	Queue<Event> userEvents = new ConcurrentLinkedQueue<Event>();

	/**
	 * add event
	 * 
	 * @param event
	 */
	public void addEvent(Event event) {
		userEvents.add(event);
	}

	/**
	 * init driver
	 * 
	 * @throws IOException
	 */
	public void init(String ip, int port) throws IOException {
		// init handler
		openHandler.init();
		closeHandler.init();
		readHandler.init();
		writeHandler.init();
		taskHandler.init();
		taskComplatedHandler.init();

		selector = Selector.open();
		socket = ServerSocketChannel.open();
		socket.socket().setReuseAddress(true);
		socket.configureBlocking(false);
		socket.socket().bind(
				new InetSocketAddress(InetAddress.getByName(ip), port));
		socket.register(selector, SelectionKey.OP_ACCEPT);
	}

	/**
	 * loop selector for listen to the event occur
	 * 
	 * @throws Exception
	 */
	public void loop() throws Exception {
		while (true) {
			selector.select();
			initIoEvents();
			handlerEvents();
		}
	}

	/**
	 * handler the event which is created by user
	 * 
	 * @throws Exception
	 */
	void handlerEvents() throws Exception {
		Event event = userEvents.poll();
		while (event != null) {
			try {
				dispatcher(event);
			} catch (Exception e) {
				// if some exception raise,then add close event in front of the
				// queue to close the
				// connection
				if (event.type != Event.Type.CLOSE) {
					Event closeEvent = new Event();
					closeEvent.type = Event.Type.CLOSE;
					closeEvent.conn = event.conn;
					addEvent(closeEvent);
				}
			}
			event = userEvents.poll();
		}
	}

	/**
	 * handler the event which is created by the connection net io event
	 * 
	 * @throws IOException
	 */
	void initIoEvents() throws IOException {
		Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator();
		while (keyIter.hasNext()) {
			SelectionKey key = keyIter.next();
			keyIter.remove();

			if (key.isAcceptable()) {
				// connection open
				Connection conn = new Connection();
				conn.socket = socket.accept();
				conn.driver = this;
				conn.selector = selector;
				currentConnectionCount++;

				if (currentConnectionCount >= maxConnection - 1) {
					// exceed max connection
					conn.close();
				} else {
					Event event = new Event();
					event.type = Event.Type.OPEN;
					event.conn = conn;
					addEvent(event);
				}
			} else if (key.isReadable()) {
				// connection is readable
				Event event = new Event();
				event.type = Event.Type.READ;
				event.conn = (Connection) key.attachment();
				addEvent(event);
			} else if (key.isWritable()) {
				// connection is writeable
				Event event = new Event();
				event.type = Event.Type.WRITE;
				event.conn = (Connection) key.attachment();
				addEvent(event);
			}
		}
	}

	/**
	 * dispatcher which handler to deal with the event
	 * 
	 * @throws Exception
	 */
	public void dispatcher(Event event) throws Exception {
		logger.debug("ENTER");
		logger.debug(event);

		switch (event.type) {
		case OPEN:
			openHandler.execute(event);
			break;
		case CLOSE:
			closeHandler.execute(event);
			break;
		case READ:
			readHandler.execute(event);
			break;
		case WRITE:
			writeHandler.execute(event);
			break;
		case TASK_EXECUTE:
			taskHandler.execute(event);
			break;
		case TASK_COMPLATED:
			taskComplatedHandler.execute(event);
			break;
		default:
			throw new Exception("Invalid Event Type");
		}

		logger.debug("LEAVE");
	}

}
